#ifndef _ELF_H_
#define _ELF_H_

#include <cstdint>

namespace analyzer::posix
{

const auto ei_nident = 16;

const auto ei_mag0 = 0;
const auto ei_mag1 = 1;
const auto ei_mag2 = 2;
const auto ei_mag3 = 3;
const auto ei_class = 4;
const auto ei_data = 5;
const auto ei_version = 6;
const auto ei_osabi = 7;
const auto ei_abiversion = 8;
const auto ei_pad = 9;

const auto elf_class_none = 0;
const auto elf_class32 = 1;
const auto elf_class64 = 2;

const auto et_none = 0;
const auto et_rel = 1;
const auto et_exec = 2;
const auto et_dyn = 3;
const auto et_core = 4;
const auto et_loos = 0xfe00;
const auto et_hios = 0xfeff;
const auto et_loproc = 0xff00;
const auto et_hiproc = 0xffff;

const auto sht_null = 0;
const auto sht_progbits = 1;
const auto sht_symtab = 2;
const auto sht_strtab = 3;
const auto sht_rela = 4;
const auto sht_hash = 5;
const auto sht_dynamic = 6;
const auto sht_note = 7;
const auto sht_nobits = 8;
const auto sht_rel = 9;
const auto sht_shlib = 10;
const auto sht_dynsym = 11;
const auto sht_init_array = 14;
const auto sht_fini_array = 15;
const auto sht_preinit_array = 16;
const auto sht_group = 17;
const auto sht_symtab_shndx = 18;
const auto sht_loos = 0x60000000;
const auto sht_hios = 0x6fffffff;
const auto sht_loproc = 0x70000000;
const auto sht_hiproc = 0x7fffffff;
const auto sht_louser = 0x80000000;
const auto sht_hiuser = 0xffffffff;

const auto shf_write = 0x1;
const auto shf_alloc = 0x2;
const auto shf_execinstr = 0x4;
const auto shf_merge = 0x10;
const auto shf_strings = 0x20;
const auto shf_info_link = 0x40;
const auto shf_link_order = 0x80;
const auto shf_os_noncomforming = 0x100;
const auto shf_group = 0x200;
const auto shf_tls = 0x400;
const auto shf_maskos = 0x0ff00000;
const auto shf_maskproc = 0xf0000000;

const auto pt_null = 0;
const auto pt_load = 1;
const auto pt_dynamic = 2;
const auto pt_interp = 3;
const auto pt_note = 4;
const auto pt_shlib = 5;
const auto pt_phdr = 6;
const auto pt_loproc = 0x70000000;
const auto pt_hiproc = 0x7fffffff;

const auto dt_null = 0;
const auto dt_needed = 1;
const auto dt_pltrelsz = 2;
const auto dt_pltgot = 3;
const auto dt_hash = 4;
const auto dt_strtab = 5;
const auto dt_symtab = 6;

namespace details
{

template <typename half, typename word, typename addr, typename off>
struct alignas(1) elf_header
{
    uint8_t e_ident[ei_nident];
    half e_type;
    half e_machine;
    word e_version;
    addr e_entry;
    off e_phoff;
    off e_shoff;
    word e_flags;
    half e_ehsize;
    half e_phentsize;
    half e_phnum;
    half e_shentsize;
    half e_shnum;
    half e_shstrndx;
};

template <typename word, typename xword, typename addr, typename off>
struct alignas(1) elf_section
{
    word sh_name;
    word sh_type;
    xword sh_flags;
    addr sh_addr;
    off sh_offset;
    xword sh_size;
    word sh_link;
    word sh_info;
    xword sh_addralign;
    xword sh_entsize;
};

} // namespace details

struct alignas(1) elf_symbol_table32
{
    uint32_t st_name;
    uint32_t st_value;
    uint32_t st_size;
    uint8_t st_info;
    uint8_t st_other;
    uint16_t st_shndx;
};

struct alignas(1) elf_symbol_table64
{
    uint32_t st_name;
    uint8_t st_info;
    uint8_t st_other;
    uint16_t st_shndx;
    uint64_t st_value;
    uint64_t st_size;
};

struct alignas(1) elf_program_header32
{
    uint32_t p_type;
    uint32_t p_offset;
    uint32_t p_vaddr;
    uint32_t p_paddr;
    uint32_t p_filesz;
    uint32_t p_memsz;
    uint32_t p_flags;
    uint32_t p_align;
};

struct alignas(1) elf_program_header64
{
    uint32_t p_type;
    uint32_t p_flags;
    uint64_t p_offset;
    uint64_t p_vaddr;
    uint64_t p_paddr;
    uint64_t p_filesz;
    uint64_t p_memsz;
    uint64_t p_align;
};

struct alignas(1) elf_dynamic_section32
{
    int32_t d_tag;
    union
    {
        int32_t d_val;
        uint32_t d_ptr;
    } d_un;
};

struct alignas(1) elf_dynamic_section64
{
    int64_t d_tag;
    union
    {
        uint64_t d_val;
        uint64_t d_ptr;
    } d_un;
};

using elf32 = details::elf_header<uint16_t, uint32_t, uint32_t, uint32_t>;
using elf64 = details::elf_header<uint16_t, uint32_t, uint64_t, uint64_t>;
using elf_section32 = details::elf_section<uint32_t, uint32_t, uint32_t, uint32_t>;
using elf_section64 = details::elf_section<uint32_t, uint64_t, uint64_t, uint64_t>;

} // namespace analyzer::posix

#endif
